#ifndef Vector_h
#define Vector_h

namespace TL {
    template<class T> class Vector {
    protected:
        T *m_Ptr;
        T m_Undefined;
        int m_Capacity, m_Size;

    protected:
        Vector(int capacity, int size, T *ptr) : m_Undefined(T())
        {
            m_Capacity = capacity;
            m_Size = size;

            m_Ptr = ptr;
        }

    public:
        Vector() : m_Undefined(T())
        {
            m_Capacity = 0;
            m_Size = 0;

            m_Ptr = 0;
        }
        Vector(int capacity) : m_Undefined(T())
        {
            m_Capacity = capacity > 0 ? capacity : 0;
            m_Size = 0;

            m_Ptr = m_Capacity > 0 ? new T[m_Capacity] : 0;
        }
        Vector(int size, const T &init_value) : m_Undefined(T())
        {
            m_Capacity = size > 0 ? size : 0;
            m_Size = size > 0 ? size : 0;

            m_Ptr = m_Capacity > 0 ? new T[m_Capacity] : 0;

            for (int i = 0; i < m_Size; i++) {
                m_Ptr[i] = init_value;
            }
        }
        Vector(const Vector &vector) : m_Undefined(T())
        {
            m_Capacity = vector.m_Capacity;
            m_Size = vector.m_Size;

            m_Ptr = m_Capacity > 0 ? new T[m_Capacity] : 0;

            for (int i = 0; i < m_Size; i++) {
                m_Ptr[i] = vector.m_Ptr[i];
            }
        }
        ~Vector()
        {
            if (m_Capacity > 0) {
                delete[] m_Ptr;
            }
        }

        T &At(int index)
        {
            if (index < 0 || index >= m_Size) {
                return m_Undefined;
            }

            return m_Ptr[index];
        }
        const T &At(int index) const
        {
            if (index < 0 || index >= m_Size) {
                return m_Undefined;
            }

            return m_Ptr[index];
        }
        int Capacity() const
        {
            return m_Capacity;
        }
        void Clear()
        {
            m_Size = 0;
        }
        void Insert(const T &value)
        {
            if (m_Size == m_Capacity) {
                T *old_ptr = m_Ptr;
                m_Ptr = new T[m_Capacity + 1];

                for (int i = 0; i < m_Size; i++) {
                    m_Ptr[i] = old_ptr[i];
                }

                if (m_Capacity > 0) {
                    delete[] old_ptr;
                }

                m_Capacity++;
            }

            m_Ptr[m_Size++] = value;
        }
        void Insert(int index, const T &value)
        {
            if (index < 0 || index > m_Size) {
                return;
            }

            if (m_Size < m_Capacity) {
                for (int i = m_Size; i > index; i--) {
                    m_Ptr[i] = m_Ptr[i - 1];
                }
            }
            else {
                T *old_ptr = m_Ptr;
                m_Ptr = new T[m_Capacity + 1];

                for (int i = 0; i < index; i++) {
                    m_Ptr[i] = old_ptr[i];
                }

                for (int i = index; i < m_Size; i++) {
                    m_Ptr[i + 1] = old_ptr[i];
                }

                if (m_Capacity > 0) {
                    delete[] old_ptr;
                }

                m_Capacity++;
            }

            m_Ptr[index] = value;

            m_Size++;
        }
        void Remove(int index)
        {
            if (index < 0 || index >= m_Size) {
                return;
            }

            for (int i = index + 1; i < m_Size; i++) {
                m_Ptr[i - 1] = m_Ptr[i];
            }

            m_Size--;
        }
        void Reserve(int capacity)
        {
            if (capacity <= m_Capacity || capacity < 1) {
                return;
            }

            T *old_ptr = m_Ptr;
            m_Ptr = new T[capacity];

            for (int i = 0; i < m_Size; i++) {
                m_Ptr[i] = old_ptr[i];
            }

            if (m_Capacity > 0) {
                delete[] old_ptr;
            }

            m_Capacity = capacity;
        }
        void Shrink()
        {
            if (m_Capacity == m_Size) {
                return;
            }

            T *old_ptr = m_Ptr;
            m_Ptr = new T[m_Size];

            for (int i = 0; i < m_Size; i++) {
                m_Ptr[i] = old_ptr[i];
            }

            if (m_Capacity > 0) {
                delete[] old_ptr;
            }

            m_Capacity = m_Size;
        }
        int Size() const
        {
            return m_Size;
        }

        Vector operator+(const Vector &vector)
        {
            int size = m_Size + vector.m_Size;

            T *new_ptr = new T[capacity];

            for (int i = 0; i < m_Size; i++) {
                new_ptr[i] = m_Ptr[i];
            }

            for (int i = 0; i < vector.m_Size; i++) {
                new_ptr[m_Size + i] = vector.m_Ptr[i];
            }

            return Vector(size, size, new_ptr);
        }
        Vector &operator+=(const Vector &vector)
        {
            Reserve(m_Size + vector.m_Size);

            for (int i = 0; i < vector.m_Size; i++) {
                m_Ptr[m_Size + i] = vector.m_Ptr[i];
            }

            m_Size += vector.m_Size;

            return *this;
        }
        Vector &operator=(const Vector &vector)
        {
            Reserve(vector.m_Size);

            for (int i = 0; i < vector.m_Size; i++) {
                m_Ptr[i] = vector.m_Ptr[i];
            }

            m_Size = vector.m_Size;

            return *this;
        }
        T &operator[](int index)
        {
            return m_Ptr[index];
        }
        const T &operator[](int index) const
        {
            return m_Ptr[index];
        }
    };
}

#endif
